home *** CD-ROM | disk | FTP | other *** search
/ Linux Cubed Series 7: Sunsite / Linux Cubed Series 7 - Sunsite Vol 1.iso / system / shells / scsh-0.4 / scsh-0 / scsh-0.4.2 / unix.c < prev   
C/C++ Source or Header  |  1995-11-04  |  17KB  |  655 lines

  1. /* Copyright (c) 1993, 1994 by Richard Kelsey and Jonathan Rees.
  2.    See file COPYING. */
  3.  
  4. /*
  5.    If you have concrete suggestions for improvements, they are quite
  6.    welcome.  Please send them to scheme-48-bugs@martigny.ai.mit.edu.
  7.  
  8.    Non-Posixisms:
  9.  
  10.    The most annoying thing here is char_ready_p, which is needed for
  11.    R^nRS and is nonportable in two different ways.  There's no
  12.    portable (Posix or anything else) way to figure whether a stdio
  13.    stream has buffered input.  And even if we knew that, there's no
  14.    portable way to find out whether input would block - select() seems
  15.    to be a BSD thing, and AT&T's ioctl() doesn't work for arbitrary
  16.    devices.  If it can't figure out what to do, it just prints a
  17.    warning and returns #t.
  18.  
  19.    setitimer(), a Berkeleyism, is used if it's available; otherwise
  20.    alarm() is used, which only has 1-second resolution.  Timer
  21.    interrupts are used by the threads apckage but not by the base
  22.    system.
  23.  
  24.    gettimeofday() is BSD.  ftime() is Version 7 (!).  The POSIX.1/ANSI
  25.    C alternative to these is time(), which returns a number of
  26.    seconds.  There seems to be some disagreement over the number of
  27.    arguments to gettimeofday().
  28.  
  29.    nlist() derives from ancient Version 6 and 7 unix, so it's pretty
  30.    widespread, but everyone wants to phase it out because it's not
  31.    very abstract.
  32.  
  33.    Access to a COFF symbol table via ldtbread is even less abstract,
  34.    really sucks in general, and should be banned from earth.
  35.    Probably only AIX needs it because of the broken nlist.
  36.  
  37.    Posix/ANSI C things used:
  38.      feof fopen fprintf perror strlen strncpy etc.
  39.      time (if gettimeofday and ftime are unavailable)
  40.  
  41.    Posix things used: (beware, PC and Mac hackers)
  42.      alarm (if setitimer is unavailable)
  43.      fileno (but only when select is being used)
  44.      getenv getpwnam sigaction sysconf
  45.      times  -- clock() is ANSI but wraps around every 36 minutes
  46.  
  47.    Other things used (BSD etc.), only when available:
  48.      ftime (if gettimeofday is unavailable)
  49.      gettimeofday
  50.      nlist
  51.      select
  52.      setitimer
  53.  
  54.    <time.h> is ANSI C, but we apparently don't use anything from it
  55.    that is part of ANSI C.  Under HPUX, the man pages tell one to
  56.    use it in order to get declarations for the things that under SunOS
  57.    are declared in <sys/time.h>.  Oh well, it can't hurt, can it?  Oh
  58.    yeah, this is Unix, of course it can...
  59. */
  60.  
  61. #include "sysdep.h"
  62.  
  63. #include <stdio.h>
  64. #include <stdlib.h>        /* for getenv(), etc. (POSIX?/ANSI) */
  65. #include <string.h>        /* for strncpy(), etc. (POSIX/ANSI) */
  66. #include <pwd.h>        /* for getpwnam() (POSIX.1) */
  67. #include <unistd.h>        /* for sysconf() (POSIX.1/.2)*/
  68. #include <sys/times.h>        /* for times() (POSIX.1) */
  69. #include <signal.h>        /* for sigaction() (POSIX.1) */
  70.  
  71. #if defined(HAVE_POSIX_TIME_H)
  72. #  include <posix/time.h>    /* RISC/OS + gcc lossage */
  73. #  define _XOPEN_SOURCE 1
  74. #else
  75. #  include <time.h>
  76. #endif
  77.  
  78. #if defined(HAVE_SETITIMER) || defined(HAVE_GETTIMEOFDAY)
  79. #  include <sys/time.h>        /* for struct itimerval, ITIMER_REAL (Sun) */
  80. #endif
  81.  
  82. #if defined(HAVE_SELECT)
  83. #  include <sys/types.h>    /* for FD_SET and friends (BSD) */
  84. #if defined(HAVE_SYS_SELECT_H)
  85. #  include <sys/select.h>
  86. #endif
  87. #endif
  88.  
  89. #if defined(HAVE_SYS_TIMEB_H)
  90. #  include <sys/timeb.h>    /* for ftime() */
  91. #endif
  92.  
  93. #if defined(HAVE_NLIST)
  94. #  include <nlist.h>        /* conforms to "SVID2", whatever that is */
  95. #endif
  96.  
  97.  
  98. #define INTERRUPT_ALARM     0    /* Cf. rts/arch.scm */
  99. #define INTERRUPT_KEYBOARD  1
  100.  
  101. extern long Spending_interruptsS;
  102.  
  103.  
  104. /* Signal handlers */
  105.  
  106. static RETSIGTYPE
  107. when_keyboard_interrupt(sig, code, scp)
  108.      int sig, code; 
  109.      struct sigcontext *scp;
  110. {
  111.   Spending_interruptsS |= (1 << INTERRUPT_KEYBOARD);
  112.   /* The following might be necessary with signal(), but shouldn't be
  113.      with sigaction() (I think) */
  114.   /* sigaction(SIGINT, &keyboard_action, NULL); */
  115.   return;
  116. }
  117.  
  118. static RETSIGTYPE
  119. when_alarm_interrupt(sig, code, scp)
  120.      int sig, code; 
  121.      struct sigcontext *scp;
  122. {
  123.   Spending_interruptsS |= (1 << INTERRUPT_ALARM);
  124.   return;
  125. }
  126.  
  127.  
  128. /* OS-dependent initialization */
  129.  
  130. static struct sigaction keyboard_action;
  131. static struct sigaction alarm_action;
  132.  
  133. void
  134. sysdep_init()
  135. {
  136.   keyboard_action.sa_handler = when_keyboard_interrupt;
  137.   keyboard_action.sa_flags = 0;
  138.   sigemptyset(&keyboard_action.sa_mask);
  139.  
  140.   alarm_action.sa_handler = when_alarm_interrupt;
  141.   alarm_action.sa_flags = 0;
  142.   sigemptyset(&alarm_action.sa_mask);
  143.  
  144.   sigaction(SIGINT, &keyboard_action, NULL);
  145.  
  146.   /* SIGPIPE's are bogus. -Olin */
  147.   {struct sigaction sa;
  148.    sa.sa_handler = SIG_IGN;
  149.    sigemptyset(&sa.sa_mask);
  150.    sa.sa_flags = 0;
  151.    sigaction(SIGPIPE, &sa, NULL);    
  152.    }
  153. }
  154.  
  155. /* ---------------------------------------- */
  156. /* For char-ready? */
  157.  
  158. int
  159. char_ready_p( FILE* stream )
  160. {
  161.   struct timeval timeout;
  162.   static int warnedp = 0;
  163.  
  164.   if (feof(stream))
  165.     return EOF;
  166.  
  167. #ifndef KLUDGY
  168.   if (fbufcount(stream) > 0)
  169.     return 1;
  170. #else
  171.   /* Grossly unportable examination of stdio buffer internals. */
  172. #if defined(FILE_HAS__CNT)
  173.   if (stream->_cnt)
  174.     return 1;
  175. #elif defined(__linux__)
  176.   if (stream->_IO_read_ptr < stream->_IO_read_end)
  177.     return 1;
  178. #elif defined(BSD) && (BSD >= 199306)
  179.   if (stream->_r)
  180.     return 1;
  181. #else
  182.  
  183.   /* Add new cases here AND SEND THEM TO scheme-48@martigny.ai.mit.edu
  184.      SO THAT THEY CAN GO INTO THE NEXT RELEASE!  (That means you, Olin.)
  185.      It's generally pretty easy to figure out what to put here by
  186.      examining /usr/include/stdio.h.  If the input stream's buffer is
  187.      nonempty, just return any positive value. */
  188.   if (!warnedp) {
  189.     fprintf(stderr, "Warning: incomplete char-ready? implementation.\n");
  190.     warnedp = 1; }
  191.   return 1;
  192. #endif
  193. #endif
  194.  
  195.   /* Nothing in the buffer.  Find out whether a read would block. */
  196. #if defined(HAVE_SELECT)
  197.   {  fd_set readfds;
  198.      FD_ZERO(&readfds);
  199.      FD_SET(fileno(stream), &readfds);
  200.      timerclear(&timeout);
  201.      return select1(FD_SETSIZE, &readfds, NULL, NULL, &timeout);
  202.      }
  203. #else /* No select() - but there will generally be some other way to do this.*/
  204.   if (!warnedp) {
  205.     fprintf(stderr, "Warning: incomplete char-ready? implementation.\n");
  206.     warnedp = 1; }
  207.   return 1;
  208. #endif
  209. }
  210.  
  211. /* ---------------------------------------- */
  212. /* For open-xxput-file */
  213.  
  214. FILE *
  215. ps_open(char *filename, char *spec)
  216. {
  217. # define FILE_NAME_SIZE 256
  218.   char filename_temp[FILE_NAME_SIZE];
  219.   char *expanded;
  220.   extern char *expand_file_name(char *, char *, int);
  221.  
  222.   expanded = expand_file_name(filename, filename_temp, FILE_NAME_SIZE);
  223.   if (expanded == NULL)
  224.     return NULL;
  225.   return fopen(expanded, spec);
  226. }
  227.  
  228. /*
  229.    Expanding Unix filenames
  230.    Unix Sucks
  231.    Richard Kelsey Wed Jan 17 21:40:26 EST 1990
  232.    Later modified by others who wish to remain anonymous
  233.    "ps_" stands for "Pre-Scheme"
  234.  
  235.    Expands initial ~ and ~/ in string `name', leaving the result in `buffer'.
  236.    `buffer_len' is the length of `buffer'.
  237.  
  238.    Note: strncpy(x, y, n) copies from y to x.
  239. */
  240.  
  241. char *
  242. expand_file_name (char *name, char *buffer, int buffer_len)
  243. {
  244. # define USER_NAME_SIZE 256
  245.   char *dir, *p, user_name[USER_NAME_SIZE];
  246.   struct passwd *user_data;
  247.   int dir_len, i;
  248.   extern char *getenv();
  249.   int name_len = strlen(name);
  250.  
  251.   dir = 0;
  252.  
  253.   if (name[0] == '~') {
  254.     name++; name_len--;
  255.  
  256.     if (name[0] == '/' || name[0] == 0) {
  257.       dir = getenv("HOME"); }
  258.  
  259.     else {
  260.       for (i = 0, p = name; i < name_len && *p != '/'; i++, p++)
  261.     if (i > (USER_NAME_SIZE - 2)) {
  262.       fprintf(stderr,
  263.           "\nexpand_file_name: user name longer than %d characters\n",
  264.           USER_NAME_SIZE - 3);
  265.       return(NULL); };
  266.       strncpy(user_name, name, i);
  267.       user_name[i] = 0;
  268.       user_data = getpwnam(user_name);
  269.       if (!user_data) {
  270.     fprintf(stderr, "\nexpand_file_name: unknown user \"%s\"\n",
  271.         user_name);
  272.     return(NULL); };
  273.       name_len -= i;
  274.       name = p;
  275.       dir = user_data->pw_dir; } }
  276.  
  277.   else if (name[0] == '$') {
  278.     name++; name_len--;
  279.  
  280.     for (i = 0, p = name; i < name_len && *p != '/'; i++, p++)
  281.       if (i > (USER_NAME_SIZE - 2)) {
  282.     fprintf(stderr,
  283.         "\nexpand_file_name: environment variable longer than %d characters\n",
  284.         USER_NAME_SIZE - 3);
  285.     return(NULL); };
  286.     strncpy(user_name, name, i);
  287.     user_name[i] = 0;
  288.  
  289.     name_len -= i;
  290.     name = p;
  291.     dir = getenv(user_name); }
  292.  
  293.   if (dir) {
  294.     dir_len = strlen(dir);
  295.     if ((name_len + dir_len + 1) > buffer_len) {
  296.       fprintf(stderr, "\nexpand_file_name: supplied buffer is too small\n");
  297.       return(NULL); };
  298.     strncpy(buffer, dir, dir_len);
  299.     strncpy(buffer + dir_len, name, name_len);
  300.     buffer[name_len + dir_len] = 0; }
  301.  
  302.   else {
  303.     if ((name_len + 1) > buffer_len) {
  304.       fprintf(stderr, "\nexpand_file_name: supplied buffer is too small\n");
  305.       return(NULL); };
  306.     strncpy(buffer, name, name_len);
  307.     buffer[name_len] = 0; }
  308.  
  309.   return(buffer);
  310. }
  311.  
  312. /* test routine
  313. main(argc, argv)
  314.   int argc;
  315.   char *argv[];
  316. {
  317.   char buffer[32];
  318.   expand_file_name(argv[1], buffer, 32);
  319.   printf("%s\n", buffer);
  320.   return(0);
  321. }
  322. */
  323.  
  324. /* ---------------------------------------- */
  325. /* Timer functions, for the time instruction.
  326.    gettimeofday() version courtesy Basile Starynkevitch.
  327.  
  328.    From: Jim.Rees@umich.edu
  329.    Date: Sun, 26 Dec 93 16:06:08 EST
  330.  
  331.    In unix.c, the gettimeofday code is wrong.  ...
  332.    I suggest the following fix, which will work on both sysV and bsd
  333.    machines.  If you (or your compiler) are squeamish about passing
  334.    too many parameters in to a system call, you might want to do it
  335.    differently....
  336.  */
  337.  
  338. #define TICKS_PER_SECOND 1000    /* should agree with ps_real_time() */
  339.  
  340. long
  341. ps_real_time()
  342. {
  343. #if defined(HAVE_GETTIMEOFDAY)
  344.   struct timeval tv;
  345.   static struct timeval tv_orig;
  346.   static int initp = 0;
  347.   if (!initp) {
  348.     gettimeofday(&tv_orig, NULL);
  349.     initp = 1;
  350.   };
  351.   gettimeofday(&tv, NULL);
  352.   return ((long)((tv.tv_sec - tv_orig.tv_sec)*TICKS_PER_SECOND
  353.          + (tv.tv_usec - tv_orig.tv_usec)/(1000000/TICKS_PER_SECOND)));
  354. #elif defined(HAVE_FTIME)
  355.   struct timeb tb;
  356.   static struct timeb tb_origin;
  357.   static int initp = 0;
  358.   if (!initp) {
  359.     ftime(&tb_origin);
  360.     initp = 1;
  361.   }
  362.   ftime(&tb);
  363.   return((long)((tb.time - tb_origin.time) * TICKS_PER_SECOND
  364.         + (tb.millitm / (1000 / TICKS_PER_SECOND))));
  365. #else
  366.   return (long)time(NULL) * TICKS_PER_SECOND;
  367. #endif /*HAVE_GETTIMEOFDAY */
  368. }
  369.  
  370. long
  371. ps_run_time()
  372. {
  373.   struct tms time_buffer;
  374.   static long clock_tick = 0;
  375.  
  376.   if (clock_tick == 0)
  377.     clock_tick = sysconf(_SC_CLK_TCK); /* POSIX.1, POSIX.2 */
  378.   times(&time_buffer);        /* On Sun, getrusage() would be better */
  379.   return((long)(time_buffer.tms_utime * TICKS_PER_SECOND) / clock_tick);
  380. }
  381.  
  382. long
  383. ps_ticks_per_second()
  384. {
  385.   return TICKS_PER_SECOND;
  386. }
  387.  
  388. long
  389. ps_schedule_interrupt(long delay)
  390. {
  391.   sigaction(SIGALRM, &alarm_action, NULL);
  392.  
  393. #if defined(HAVE_SETITIMER)
  394.   { struct itimerval new, old;
  395.  
  396.     delay = delay * (1000000 / TICKS_PER_SECOND);
  397.     new.it_value.tv_sec = delay / 1000000;
  398.     new.it_value.tv_usec = delay % 1000000;
  399.     new.it_interval.tv_sec = 0;
  400.     new.it_interval.tv_usec = 0;
  401.     if (0 == setitimer(ITIMER_REAL, &new, &old))
  402.       return (old.it_value.tv_usec + 1000000 * old.it_value.tv_sec)
  403.     / (1000000 / TICKS_PER_SECOND);
  404.     else {
  405.       perror("setitimer");
  406.       return -1;
  407.     }
  408.   }
  409. #else
  410.   /* Round up to nearest second.  0 means cancel... */
  411.   return alarm((delay + TICKS_PER_SECOND - 1) / TICKS_PER_SECOND)
  412.        * TICKS_PER_SECOND;
  413. #endif
  414. }
  415.  
  416. /*
  417. ** External symbol / foreign-function interface.
  418. *******************************************************************************
  419. ** This code is ifdef'd. You must arrange it so that when it is handed a C
  420. ** identifier "foo" it looks up the corresponding a.out identifier, which can
  421. ** be "_foo" or "foo" or perhaps something else entirely.
  422. **
  423. ** If USCORE is defined, then (get-external-name "main") will look up
  424. ** the a.out symbol "_main". If USCORE is undefined, the it simply looks
  425. ** up "main".
  426. **
  427. ** lookup_external_name(name, loc):
  428. ** - On success, stores location in "loc" and returns 1.
  429. ** - On failure, returns 0.
  430. */
  431.  
  432. #if defined(_AIX)
  433.  
  434. /* The problem with AIX is threefold:
  435.    - AIX throws away unused symbols during linking.
  436.      Since the externals only get referenced at runtime, ld throws them out.
  437.      This can, in theory, be turned off by the "nogc" option, but when
  438.      that is used, all hell breaks lose.
  439.    - AIX nlist lies: if you ask for "foobar", and there is a symbol "foo" in
  440.      the symbol table before "foobar", it will give you the value for "foo".
  441.    - AIX function pointers from the symbol table cannot be used directly:
  442.      Rather, all jumps are indirect.  Therefore, the externals mechanism needs
  443.      to set up an "activation record" which contains the actual pointer, followed
  444.      by a toc value which is loaded into r2 in function activation.  I'm not sure what
  445.      the third one is for.  Even then, an offset is involved which is calculated using
  446.      the data of the "main" procedure. */
  447.  
  448. #include <filehdr.h>
  449. #include <syms.h>
  450. #include <ldfcn.h>
  451.  
  452. struct s_symbol_table {
  453.     char *name;
  454.     void *value;
  455.     struct s_symbol_table *next;
  456. };
  457.  
  458. static struct s_symbol_table *the_table = NULL;
  459.  
  460. #define MALLOC_TO(name, size) \
  461.   if (((name) = malloc(size)) == NULL) { \
  462.     fprintf(stderr, "Malloc error during external name lookup\n"); \
  463.     return; \
  464.   }
  465.  
  466. static void
  467. fill_the_table(void)
  468. {
  469.   extern char *get_reloc_file();
  470.   char *reloc_info_file;
  471.   LDFILE *f;
  472.   SYMENT entry;
  473.   long i;
  474.   struct s_symbol_table *cur_entry;
  475.  
  476.   reloc_info_file = get_reloc_file();
  477.  
  478.   if (reloc_info_file == NULL) {
  479.     fprintf(stderr, "Error during external name lookup\n");
  480.     return;
  481.   }
  482.   
  483.   if ((f = ldopen(reloc_info_file, NULL)) == NULL) {
  484.     fprintf(stderr, "Error during external name lookup\n");
  485.     return;
  486.   }
  487.  
  488.   if (!ldtbseek(f)) {
  489.     ldclose(f);
  490.     fprintf(stderr, "Error during external name lookup\n");
  491.     return;
  492.   }
  493.  
  494.   cur_entry = the_table;
  495.  
  496.  
  497.   for (;;) {
  498.     i = ldtbindex(f);
  499.     if (!ldtbread(f, i, &entry))
  500.       break;
  501.     if (entry.n_sclass == C_EXT &&
  502.     entry.n_scnum != N_UNDEF &&
  503.     entry.n_scnum != N_DEBUG)
  504.       {
  505.     extern char *ldgetname(LDFILE *, SYMENT *);
  506.     char *p = ldgetname(f, &entry);
  507.  
  508.     if (p != NULL && p[0] == '.') {
  509.       struct s_symbol_table *last_entry = cur_entry;
  510.  
  511.       MALLOC_TO(cur_entry, sizeof(struct s_symbol_table));
  512.       MALLOC_TO(cur_entry->name, strlen(p));
  513.       strcpy(cur_entry->name, p+1); /* skip the dot */
  514.       cur_entry->value = (void *) entry.n_value;
  515.       if (!last_entry)
  516.         the_table = cur_entry;
  517.       else
  518.         last_entry->next = cur_entry;
  519.     }
  520.       }
  521.   }
  522.  
  523.   if (cur_entry)
  524.     cur_entry->next = NULL;
  525.     
  526.   ldclose(f);
  527. }
  528.  
  529. static long
  530. really_lookup_external_name(char *name)
  531. {
  532.   struct s_symbol_table *entry = the_table;
  533.  
  534.   while (entry) {
  535.     if (!strcmp(entry->name, name))
  536.       return((long) entry->value);
  537.     entry = entry->next;
  538.   }
  539.   return(0);
  540. }
  541.       
  542. long
  543. lookup_external_name(char *name, long *location)
  544. {
  545.   long r;
  546.   static long val_offset;
  547.   extern int main();
  548.  
  549.   if (!the_table) {
  550.     fill_the_table();
  551.     val_offset =
  552.       *((unsigned long *) ((void (*)()) main)) - really_lookup_external_name("main");
  553.   }
  554.  
  555.   r = really_lookup_external_name(name);
  556.  
  557.   if (r) {
  558.     unsigned long *activate;
  559.  
  560.     MALLOC_TO(activate, 3 * sizeof(unsigned long));
  561.     memcpy(activate, (void *) main, 3 * sizeof(unsigned long));
  562.     ((unsigned long *)activate)[0] = r + val_offset;
  563.     *location = (long) activate;
  564.     return(1);
  565.   } else
  566.     return(0);
  567. }
  568.  
  569. #else /* i.e. #if !defined(_AIX) */
  570.  
  571. #if !defined(NLIST_HAS_N_NAME)
  572. #define n_name    n_un.n_name
  573. #endif
  574.  
  575. long
  576. lookup_external_name(char *name, long *location)
  577. {
  578.   int namelen = strlen(name);
  579.   char buf[20], *nm;
  580.  
  581. #if defined(HAVE_DLOPEN)
  582.   extern int lookup_dlsym(char*, long*);
  583. #elif defined(HAVE_NLIST)
  584.   extern char *get_reloc_file();
  585.   char *reloc_info_file;
  586.   struct nlist name_list[2];
  587.   int status;
  588. #endif
  589.  
  590.   /* Compute the actual string being looked up. */
  591.   /* Compute the length of the actual a.out symbol. */
  592. #ifdef USCORE
  593.   namelen++;
  594. #endif
  595.  
  596.   nm = (namelen<20) ? buf : (char*) malloc(namelen+1);
  597.   if (!nm) {
  598.     fputs("Malloc error in lookup_external_name.\n", stderr);
  599.     return(0);
  600.   }
  601.  
  602.   /* Build the symbol in nm. */
  603. #ifdef USCORE
  604.   nm[0] = '_';
  605.   strcpy(nm+1,name);
  606. #else
  607.   strcpy(nm,name);
  608. #endif /*! HAVE_DLOPEN */
  609. #if defined(HAVE_DLOPEN)
  610.   return lookup_dlsym(nm, location);
  611. #elif defined(HAVE_NLIST)
  612.  
  613.   reloc_info_file = get_reloc_file();
  614.  
  615.   if (reloc_info_file == NULL) {
  616.     fprintf(stderr, "Error during external name lookup\n");
  617.     return(0);
  618.   }
  619.  
  620.   name_list[0].n_name = nm;
  621.   name_list[1].n_name = 0;
  622.   
  623.   status = nlist(reloc_info_file, name_list);
  624.  
  625.   if (status != 0 || (name_list[0].n_value  == 0
  626.               && name_list[0].n_type == 0))
  627.     return 0;
  628.   else {
  629.     *location = name_list[0].n_value;
  630.     return 1;
  631.   }
  632. #else
  633.   return 0;
  634. #endif /*! HAVE_DLOPEN */
  635. }
  636.  
  637. #endif /* !defined(AIX) */
  638.  
  639. extern char *object_file;   /* specified via a command line argument */
  640. extern char *reloc_file;    /* dynamic loading will set this */
  641.  
  642. char *
  643. get_reloc_file()
  644. {
  645.   if (reloc_file != NULL)
  646.     return(reloc_file);
  647.   if (object_file != NULL)
  648.     return(object_file);
  649.   else {
  650.     fprintf(stderr, "Object file not specified on command line\n");
  651.     return(NULL);
  652.   }
  653. }
  654.  
  655.